package com.wrupple.vegetate.client;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

import com.google.gwt.core.client.GWT;
import com.google.gwt.event.shared.EventBus;
import com.google.gwt.http.client.Request;
import com.google.gwt.http.client.RequestBuilder;
import com.google.gwt.http.client.RequestBuilder.Method;
import com.google.gwt.http.client.RequestCallback;
import com.google.gwt.http.client.RequestException;
import com.google.gwt.http.client.Response;
import com.google.gwt.http.client.UrlBuilder;
import com.google.gwt.jsonp.client.JsonpRequestBuilder;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.wrupple.muba.common.client.application.DataCallback;
import com.wrupple.muba.common.shared.StateTransition;
import com.wrupple.vegetate.client.event.NewVegetateRequestEvent;
import com.wrupple.vegetate.client.event.VegetateRequestFailureEvent;
import com.wrupple.vegetate.client.event.VegetateRequestSuccessEvent;
import com.wrupple.vegetate.client.module.services.SerializationService;

public class SimpleVegetateChannel<T, R > implements VegetateChannel<T, R> {

	class JavaScriptCallback implements RequestCallback , AsyncCallback<R>{

		private StateTransition<R> callback;
		private int requestNumber;

		public JavaScriptCallback(StateTransition<R> callback, int requestCount) {
			this.callback = callback;
			this.requestNumber = requestCount;
		}

		@Override
		public void onResponseReceived(Request request, Response response) {
			try {
				String json = response.getText();
				R result = serializer.deserialize(json);
				onSuccess( result);
			} catch (Exception e) {
				onError(request, e);
			}
		}

		@Override
		public void onError(Request request, Throwable exception) {
			GWT.log(request.toString(), exception);
			onFailure(exception);
		}


		@Override
		public void onSuccess(R result) {
			try{
				eventBus.fireEvent(new VegetateRequestSuccessEvent(requestNumber,channelId,result));
				callback.setResultAndFinish(result);
			} catch (Exception e) {
				onFailure(e);
			}
		}
		
		@Override
		public void onFailure(Throwable caught) {
			GWT.log("Response Processing Fail", caught);
			eventBus.fireEvent(new VegetateRequestFailureEvent(requestNumber,channelId,caught));
		}

	}
	
	
	class RemoveFromQueue extends DataCallback<R>{

		private String key;

		public RemoveFromQueue(String key) {
			this.key=key;
		}

		@Override
		public void execute() {
			throwttleMap.remove(key);
		}
		
	}


	protected String channelId ;
	// if set, JsopnP will be used
	private String host;
	// ignored if using jsonP (jsonp always uses GET)
	private Method method;
	private String vegetateUrlBase;
	protected UrlBuilder urlbuilder;
	private SerializationService<T, R> serializer;
	private int port;
	private String protocol;
	protected EventBus eventBus;
	
	/*
	 * Bottle neck
	 */
	private static int requestCount = 0;
	private final static Map<String,StateTransition<?>> throwttleMap= new HashMap<String,StateTransition<?>>();
	
	public SimpleVegetateChannel(EventBus bus,String channelId, String protocol, Method method, String channelPath,
			SerializationService<T, R> serializer, int port) {
		super();
		this.eventBus = bus;
		this.method = method;
		this.vegetateUrlBase = channelPath;
		this.serializer = serializer;
		this.port = port;
		this.channelId = channelId;
		this.protocol = protocol;
	}

	@Override
	public void send(T object, final StateTransition<R> callback,
			String... addressTokens) {
		try {
			buildUrl(object, addressTokens);
		} catch (IOException e) {
			throw new VegetateClientException(e);
		}
		String requestUrl = urlbuilder.buildString();
		StateTransition<?> transitionQueue=throwttleMap.get(requestUrl);
		
		if(transitionQueue==null){
			throwttleMap.put(requestUrl,callback);
			StateTransition<R> removeFromQueue= new RemoveFromQueue(requestUrl);
			callback.hook(removeFromQueue);
			privateSend(requestUrl,object, callback, addressTokens);
		}else{
			StateTransition<R> cast = (StateTransition<R>) transitionQueue;
			cast.hook(callback);
		}
	}
	
	private void privateSend(String url,T object, final StateTransition<R> callback,
			String... addressTokens) {
		requestCount ++;
		eventBus.fireEvent(new NewVegetateRequestEvent(requestCount,channelId,object,addressTokens,url));
		JavaScriptCallback requestCallback  =new JavaScriptCallback(callback,requestCount);
		if (Window.Location.getHostName().equals(host)) {
			RequestBuilder post = new RequestBuilder(method,url);
			post.setCallback(requestCallback);
			try {
				post.send();
			} catch (RequestException e) {
				throw new VegetateClientException(e);
			}
		} else {
			JsonpRequestBuilder jsonp = new JsonpRequestBuilder();
			AsyncCallback castedCallback=requestCallback;
			jsonp.requestObject(url,castedCallback);
		}
	}

	private void buildUrl(T object, String[] addressTokens) throws IOException {
		urlbuilder = new UrlBuilder();
		if (protocol != null) {
			urlbuilder.setProtocol(protocol);
		}

		if (host == null) {
			String portString = Window.Location.getPort();
			if(portString.isEmpty()){
				port = UrlBuilder.PORT_UNSPECIFIED;
			}else{
				port = Integer.parseInt(portString);
			}
			host = Window.Location.getHostName();
		}
		urlbuilder.setPath(buildPath(addressTokens));
		urlbuilder.setParameter(channelId, serializer.serialize(object));
		urlbuilder.setPort(port);
		urlbuilder.setHost(host);
	}

	private String buildPath(String[] addressTokens) {
		StringBuilder builder = new StringBuilder(vegetateUrlBase.length()
				+ addressTokens.length * 10);
		builder.append(this.vegetateUrlBase);

		for (String token : addressTokens) {
			builder.append('/');
			builder.append(token);
		}
		return builder.toString();
	}

	public String getHost() {
		return host;
	}

	public void setHost(String host) {
		this.host = host;
	}

	public Method getMethod() {
		return method;
	}

	public void setMethod(Method method) {
		this.method = method;
	}

	public String getVegetateUrlBase() {
		return vegetateUrlBase;
	}

	public void setVegetateUrlBase(String vegetateUrlBase) {
		this.vegetateUrlBase = vegetateUrlBase;
	}

	public SerializationService<T, R> getSerializer() {
		return serializer;
	}

	public void setSerializer(SerializationService<T, R> serializer) {
		this.serializer = serializer;
	}

	public int getPort() {
		return port;
	}

	public void setPort(int port) {
		this.port = port;
	}

	public String getProtocol() {
		return protocol;
	}

	public void setProtocol(String protocol) {
		this.protocol = protocol;
	}

}
